
//Copyright 1997-2009 Syrinx Development, Inc.
//This file is part of the Syrinx Web Application Framework (SWAF).
// == BEGIN LICENSE ==
//
// Licensed under the terms of any of the following licenses at your
// choice:
//
//  - GNU General Public License Version 3 or later (the "GPL")
//    http://www.gnu.org/licenses/gpl.html
//
//  - GNU Lesser General Public License Version 3 or later (the "LGPL")
//    http://www.gnu.org/licenses/lgpl.html
//
//  - Mozilla Public License Version 1.1 or later (the "MPL")
//    http://www.mozilla.org/MPL/MPL-1.1.html
//
// == END LICENSE ==
using System;
using System.Collections;
using System.Runtime.Serialization;
using System.Text;
using System.Xml;
using Swaf.BizObj;
using Swaf.Call;
using Swaf.Container;

namespace Swaf.Security
{
	/// <summary>
	/// Provides the basic implementation of the IResourceSet interface.  ResourceSets
	/// are simply a named list of resources.
	/// </summary>
	[Serializable]
	public class ResourceSet : IResourceSet, ISerializable
	{
		protected string m_name="";
		[NonSerialized] protected ICall m_resourceCall=null;
		protected INameAddressSupport m_resources=null;
		protected bool m_isPopulated = false;
        [NonSerialized] static bool s_Pass_Sec_Principal = true;
        protected string m_resourceCallXml = null;

        protected ResourceSet()
        {
            init();
            //Nothign to do.
        }
        
        /// <summary>
        /// Sets the value of the static s_Pass_Sec_Principal field.  Called from constructors 
        /// </summary>
        private void init()
        {
            string val = Application.currentApp.resMgr.getMacro("UseSecurityPrincipalAsParam");
            if (val != null && val != "")
            {
                try
                {
                    s_Pass_Sec_Principal = bool.Parse(val);
                }
                catch
                {
                    //Default to current behavior.
                    s_Pass_Sec_Principal = true;
                }

            }
        }

		/// <summary>
		/// Constructor used to instantiate a ResourceSet via code.
		/// </summary>
		/// <param name="name">Application unique name for this resourceset</param>
		/// <param name="resourceCall">ICall instance to be used to load resources 
		/// into this resourceset</param>
		public ResourceSet(string name, ICall resourceCall)
		{
            init();
            m_name = name;
			m_resourceCall = resourceCall;
			m_resources = new FlexiMap();
		}
		/// <summary>
		/// Constructor used by the configuration system or to deserialize a serialized 
		/// ResourceSet from an Xml node.  The passed in node can also contain resource
		/// definitions to prepopulate the resourceset.
		/// </summary>
		/// <param name="ele">XmlNode containing information about the ResourceSet</param>
		public ResourceSet(XmlElement ele)
		{
            init();
            initFromElement(ele);
		}
	    protected void initFromElement(XmlElement ele)
	    {
            IClassFactory rscFactory = Application.currentApp.factories["Resources"];
            m_name = ele.GetAttribute("name");
            m_resources = new FlexiMap();
            IResource resource = null;
            foreach (XmlNode res in ele.ChildNodes)
            {
                if (res.Name.Equals("Resources"))
                {
                    foreach (XmlNode child in res.ChildNodes)
                    {
                        if (child.NodeType == XmlNodeType.Element)
                        {
                            try
                            {
                                resource = (IResource)rscFactory.createFromNickName(child.Name, child);
                                addInfoToResource(resource);
                                m_resources.put(resource.name, resource);
                            }
                            catch (Exception e)
                            {
                                Application.currentApp.Log.println("Unable to create resource using node {0}. The error was {1}.", child.OuterXml, e.ToString());
                            }
                        }
                    }
                }
                else if (res.Name.Equals("ResourceCall"))
                {
                    foreach (XmlNode child in res.ChildNodes)
                    {
                        if (child.NodeType == XmlNodeType.Element)
                        {
                            //Store the Xml string for this ICall instance so we can recreate it later during deserialization.
                            m_resourceCallXml = child.OuterXml;
                            m_resourceCall = (ICall)Application.currentApp.factories["CallTypes"].createFromNickName(child.Name, ((XmlElement)child).GetAttribute("name"), (XmlElement)child);
                            break;
                        }
                    }
                }
                else
                {
                    processAdditionNodes(res);
                }
            }	        
	    }
	    protected virtual void addInfoToResource(IResource resource)
	    {
	        //Nothing to do in this implementation.
	    }
	    protected virtual void processAdditionNodes(XmlNode unknownNode)
	    {
	        //Nothing to do in this implementation.
	    }
		#region IResourceSet Members

		/// <summary>
		/// 			<see cref="IResourceSet.clone"/>
		/// </summary>
		/// <returns></returns>
		public IResourceSet clone()
		{
			return clone(false);
		}

		/// <summary>
		/// <see cref="IResourceSet.clone"/>
		/// </summary>
		/// <returns></returns>
		virtual public IResourceSet clone(bool deepClone)
		{
            IResourceSet cloned = (IResourceSet)Application.currentApp.factories["ResourceSets"].createFromNickName(this.name, this.name, this.resourceCall); 
			if(deepClone)
			{
				foreach(DictionaryEntry entry in (FlexiMap) resources)
				{
					IResource res = (IResource) entry.Value;
					cloned.resources.put(res.name, res.clone());
				}
			}
            cloned.resourceCallXml = resourceCallXml;
			return cloned;
		}

		/// <summary>
		/// <see cref="IResourceSet.populateResources"/>
		/// </summary>
		public virtual void populateResources()
		{
		    populateResources(null);
		}

        /// <summary>
        /// <see cref="IResourceSet.populateResources"/>
        /// </summary>
        public virtual void populateResources(INameAddressSupport additionalInfo)
        {
            object callResults = null;
            //If a value is passed in for additionalInfo, then we are passing both the
            //  security principal and an INameAddressSupport parameter to the ICall instance
            if (additionalInfo != null)
            {

                //We need to make sure that the resourceSetName is in additionalInfo.
                tryToAddField(additionalInfo, "resourceSetName", m_name,false);
                //Give derived classes an opportunity to insert needed values into the additionalInfo collection.
                addInfoForPopulateResources(additionalInfo);

                //We need to make sure that appId is in additionalInfo.
                tryToAddField(additionalInfo, "AppName", Application.currentApp.resMgr.resolve("$ApplicationId$"), false);

                if (s_Pass_Sec_Principal)
                {
                    //Make the call.
                    callResults = m_resourceCall.call(Application.currentApp.securityMgr.currentPrincipal, additionalInfo);
                }
                else
                {
                    INameAddressSupport info = additionalInfo;
                    INameAddressSupport principalInfo = Application.currentApp.securityMgr.currentPrincipal.additionalInfo;

                    //If principalInfo is not null, we need to combine the additionalInfo from the principal with the one passed in.
                    if (principalInfo != null)
                    {
                        //We need to combine the additionalInfo from the security principal with that which was provided.
                        NameAddressStack stack = new NameAddressStack(principalInfo);
                        stack.push(additionalInfo);
                        info = (INameAddressSupport)stack;
                    }
                    //Make the call.
                    callResults = m_resourceCall.call(info);

                }
            }
                //Otherwise, we are simply calling with only the security principal
            else
            {
                if (s_Pass_Sec_Principal)
                {
                    callResults = m_resourceCall.call(new object[] { Application.currentApp.securityMgr.currentPrincipal });
                }
                else
                {
                    callResults = m_resourceCall.call(new object[] { Application.currentApp.securityMgr.currentPrincipal.additionalInfo });
                }
            }
            
            INameAddressSupport vals = callResults as INameAddressSupport;
            if (vals == null)
            {
                IResults ruleResults = callResults as IResults;
                if (ruleResults != null)
                {
                    if (ruleResults.ExceptionInfo != null)
                    {
                        throw new BaseSecurityException("Error executing resourceset rule.", ruleResults.ExceptionInfo);
                    }
                    else
                    {
                        //Make sure the return type is valid.
                        vals = ruleResults.CallResults as INameAddressSupport;
                        if (vals == null)
                        {
                            throw new BaseSecurityException(
                                "ResourceSet rule either returned null or did not return an INameAddressSupport");
                        }
                    }
                }
            }
            
            //See if we have a BizObjList.  If so, we need to populate our resources from this list.
            IBizObjList list = vals as IBizObjList;

            if (list != null)
            {
                m_resources = populateResourcesFromList(list);
            }
            else
            {
                m_resources = vals;
            }

            m_isPopulated = true;
        }
	    
	    /// <summary>
	    /// This method can/should be overridden by derived classes that need to add more values to additionalInfo before 
	    /// populateResources is called.
	    /// </summary>
	    /// <param name="additionalInfo"></param>
	    virtual protected void addInfoForPopulateResources(INameAddressSupport additionalInfo)
	    {
	        //Nothing to do in the base resourceset. 
	    }
	    
	    /// <summary>
	    /// Helper method to try and set a specified field value in additionalInfo.  Works for both BizObj and FlexiMaps.
	    /// </summary>
	    /// <param name="additionalInfo">INameAddressSupport instance to add to.</param>
	    /// <param name="fieldName">Field to add</param>
	    /// <param name="value">Field value</param>
	    protected void tryToAddField(INameAddressSupport additionalInfo, string fieldName, object value, bool forceOverwrite)
	    {
            //If this is a bizobj, check to see if it has the specified field and if it has a value.
            if (additionalInfo is IBizObj && ((IBizObj)additionalInfo).type.OrderedFields.Contains(fieldName) && ((IBizObj)additionalInfo).containsField(fieldName))
            {
                additionalInfo.put(fieldName, value);
            }
            //Otherwise, if the value has not been set or forceOverwrite, set it
            else if (forceOverwrite || !additionalInfo.containsField(fieldName))
            {
                additionalInfo.put(fieldName, value);
            }
	        
	    }
		/// <summary>
		/// <see cref="IResourceSet.populateResourcesFromList"/>
		/// </summary>
		/// <param name="source"></param>
		/// <returns></returns>
		public INameAddressSupport populateResourcesFromList(IEnumerable source)
		{
			IClassFactory rscFactory = Application.currentApp.factories["Resources"];
			IResource resource=null;
			FlexiMap resources = (FlexiMap) m_resources;
			foreach(IBizObj res in source)
			{
				string bizObjName = res.type.Name;
				resource = (IResource) rscFactory.createFromNickName(bizObjName, res);
			    addInfoToResource(resource);
				resources.Add(resource.name, resource);
			}
			return resources;
		}

		/// <summary>
		/// <see cref="IResourceSet.isPopulated"/>
		/// </summary>
		public bool isPopulated
		{
			get
			{
				return m_isPopulated;
			}
		}

		/// <summary>
		/// <see cref="IResourceSet.resources"/>
		/// </summary>
		public Swaf.Container.INameAddressSupport resources
		{
			get
			{
				return m_resources;
			}
		}

		/// <summary>
		/// <see cref="IResourceSet.resourceCall"/>
		/// </summary>
		public Swaf.Call.ICall resourceCall
		{
			get
			{
				return m_resourceCall;
			}
		}

        /// <summary>
        /// <see cref="IResourceSet.resourceCallXml"/>
        /// </summary>
        public string resourceCallXml
        {
            get { return m_resourceCallXml; }
            set { m_resourceCallXml = value; }
        }

		/// <summary>
		/// <see cref="IResourceSet.name"/>
		/// </summary>
		public string name
		{
			get
			{
				return m_name;
			}
		}

	    /// <summary>
        /// <see cref="IResourceSet.getBizObjList"/>
        /// </summary>
        public IBizObjList getBizObjList()
        {
            IBizObjList list = null;
            FlexiMap resourceList = (FlexiMap) m_resources;
            foreach (DictionaryEntry entry in resourceList)
            {
                IResource rsc = (IResource) entry.Value;
                IBizObj inst = rsc.bizObj;
                if (inst != null)
                {
                    if(list == null)
                    {
                        list = Application.currentApp.bizObjMgr.createList(inst.type.Name);
                    }
                    list.Add(inst);
                }
            }
            return list;
        }
		#endregion

		#region INameAddressSupport Members

		/// <summary>
		/// <see cref="INameAddressSupport.containsField"/>
		/// </summary>
		/// <param name="nameAddress"></param>
		/// <returns></returns>
		public bool containsField(string nameAddress)
		{
			if (! isPopulated)
				populateResources();

			return m_resources.containsField(nameAddress);
		}

		/// <summary>
		/// <see cref="INameAddressSupport.isGetOnly"/>
		/// </summary>
		public bool isGetOnly
		{
			get
			{
				return true;
			}
		}

		/// <summary>
		/// <see cref="INameAddressSupport.SerializationSupport"/>
		/// </summary>
		public Swaf.Container.XmlSerializationSupport SerializationSupport
		{
			get
			{
				return XmlSerializationSupport.AsElement;
			}
		}

		/// <summary>
		/// <see cref="INameAddressSupport.put"/>
		/// </summary>
		/// <param name="nameAddress"></param>
		/// <param name="val"></param>
		/// <param name="extraInfo"></param>
		public void put(string nameAddress, object val, object extraInfo)
		{
			// Nothing to do here since Puts are not supported.
		}

		/// <summary>
		/// <see cref="INameAddressSupport.put"/>
		/// </summary>
		/// <param name="nameAddress"></param>
		/// <param name="val"></param>
		void Swaf.Container.INameAddressSupport.put(string nameAddress, object val)
		{
			// Nothing to do here since Puts are not supported.
		}

		/// <summary>
		/// <see cref="INameAddressSupport.getXml"/>
		/// </summary>
		/// <param name="options"></param>
		/// <returns></returns>
		public string getXml(string options)
		{
			if (! isPopulated)
				populateResources();

			StringBuilder buffer = new StringBuilder("<ResourceSet name='" + m_name + "'>");
			buffer.Append(m_resources.getXml("parent(Resources)"));
			buffer.Append("</ResourceSet>");
			return buffer.ToString();
		}

		/// <summary>
		/// <see cref="INameAddressSupport.get"/>
		/// </summary>
		/// <param name="nameAddress"></param>
		/// <returns></returns>
		virtual public object get(string nameAddress)
		{
			//If this is a single request go ahead and return this.
			if (nameAddress == null || nameAddress.Equals(""))
				return this;
            //Only allow one thread at a time into this block to keep concurrent 
            //  requests from trying to popluate the resources.
            lock (this)
            {
                if (!isPopulated)
                    populateResources();
            }
			object ret =  m_resources.get(nameAddress);
			if (ret == null)
				return Privileges.None;
			else
				return ret;
		}

		/// <summary>
		/// <see cref="INameAddressSupport.get"/>
		/// </summary>
		/// <param name="nameAddress"></param>
		/// <param name="oneArg"></param>
		/// <returns></returns>
		object Swaf.Container.INameAddressSupport.get(string nameAddress, object oneArg)
		{
			return get(nameAddress);
		}

		/// <summary>
		/// <see cref="INameAddressSupport.get"/>
		/// </summary>
		/// <param name="nameAddress"></param>
		/// <param name="infoArgs"></param>
		/// <returns></returns>
		object Swaf.Container.INameAddressSupport.get(string nameAddress, params object[] infoArgs)
		{
			return get(nameAddress);
		}

		/// <summary>
		/// <see cref="INameAddressSupport.Xml"/>
		/// </summary>
		public string Xml
		{
			get
			{
				return getXml("");
			}
		}

		#endregion
		public ResourceSet(System.Runtime.Serialization.SerializationInfo info, StreamingContext context)
		{
            init();
            m_name = info.GetString("name");
			m_isPopulated = info.GetBoolean("populated");
			m_resources = (INameAddressSupport) info.GetValue("resources", typeof(FlexiMap));
            m_resourceCallXml = (string)info.GetString("resourceCallXml");
			//Since we can't serialize the ICall instance, we need to set it for this principal from the 
			//one loaded at startup.
			//ResourceSet localInstance = (ResourceSet) ((FlexiMap)Application.currentApp.securityMgr.resourceSets)[m_name];
			//m_resourceCall = localInstance.m_resourceCall;
            try
            {
                XmlDocument doc = new XmlDocument();
                doc.LoadXml(m_resourceCallXml);
                XmlElement child = (XmlElement)doc.FirstChild;
                m_resourceCall = (ICall)Application.currentApp.factories["CallTypes"].createFromNickName(child.Name, child.GetAttribute("name"), child);
            }
            catch (Exception e)
            {
                Application.currentApp.Log.write("Unable to create the resourceCall class during deserialization of ResourceSet class.  The exception was {0}", e.ToString());
            }
		}
		#region ISerializable Members

		public void GetObjectData(System.Runtime.Serialization.SerializationInfo info, StreamingContext context)
		{
			info.AddValue("name", m_name);
			info.AddValue("populated", m_isPopulated);
			info.AddValue("resources", m_resources);
            info.AddValue("resourceCallXml", m_resourceCallXml);
		}

		#endregion
		public IEnumerator GetEnumerator() { return null; }
	}
}
